﻿using NModbus;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace FilmBlowingMachine.Winform.Lib
{
    public class ModbusTcp
    {
        private ModbusFactory _modbusFactory;
        private IModbusMaster _master;
        private TcpClient _tcpClient;

        public string IpAddress { get; set; }
        public int Port { get; set; }
        public byte SlaveAddress { get; set; }
        public string ErrorCode { get; set; }


        public bool IsConnected
        {
            get
            {
                try
                {

                    if (!(_tcpClient?.Connected ?? false))
                    {
                        return false;
                    }

                    var result = _master?.ReadCoils(SlaveAddress, 1, 1)[0];
                    return result != null;
                }
                catch (Exception)
                {
                    return false;
                }
            }
        }

        public ModbusTcp(string ip, int port, byte slaveAddress)
        {
            IpAddress = ip;
            Port = port;
            SlaveAddress = slaveAddress;
        }
         
        public bool Connect()
        {
            _modbusFactory = new ModbusFactory();
            _tcpClient = new TcpClient();

            if (!_tcpClient.ConnectAsync(IpAddress, Port).Wait(TimeSpan.FromSeconds(1)))
            {
                return false;
            }

            _master = _modbusFactory.CreateMaster(_tcpClient);
            _master.Transport.ReadTimeout = 500;
            _master.Transport.Retries = 3;
            return true;
        }

        public bool Disconnect()
        {
            if (_tcpClient.Connected)
            {
                _tcpClient.Close();
                _master.Dispose();
                return true;
            }
            return false;
        }

        public void KeepAlive()
        {
            if (!(_tcpClient?.Connected ?? false))
            {
                _tcpClient.Close();
                Connect();
            }
            
        }

        public TResult<bool> ReadSingleCoil(ushort startAddress)
        {
            var res = new TResult<bool>();
            try
            {
                res.Value = _master.ReadCoils(SlaveAddress, startAddress, 1)[0];
                res.Success = true;
            }
            catch (Exception)
            {
                
            }

            return res;
        }

        public TResult<bool[]> ReadCoils(ushort startAddress, ushort num)
        {
            var res = new TResult<bool[]>();

            try
            {
                res.Value = _master.ReadCoils(SlaveAddress, startAddress, num);
                res.Success = true;
            }
            catch (Exception)
            {
                
            }

            return res;
        }

        /// <summary>
        /// 读取单个16bits无符号整数
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        /// <returns>16bits 无符号整数</returns>
        public TResult<ushort> ReadSingleWord(ushort startAddress)
        {
            var res = new TResult<ushort>();

            try
            {
                res.Value = _master.ReadHoldingRegisters(SlaveAddress, startAddress, 1)[0];
                res.Success = true;
            }
            catch (Exception)
            {
                
            }

            return res;
        }

        /// <summary>
        /// 读取单个32bits整数
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        /// <returns>32bits 整数</returns>
        public TResult<int> ReadDWord(ushort startAddress)
        {
            var res = new TResult<int>();

            try
            {
                ushort[] buf = _master.ReadHoldingRegisters(SlaveAddress, startAddress, 2);
                res.Value = BitConverter.ToInt32(BitConverter.GetBytes(buf[0] | (uint)(buf[1] << 16)), 0);
                res.Success = true;
            }
            catch (Exception)
            {
                
            }

            return res;
        }

        /// <summary>
        /// 读取单个32bits浮点数
        /// </summary>
        /// <param name="startAddress"></param>
        /// <returns>32bits float</returns>
        public TResult<float> ReadFloat(ushort startAddress)
        {
            var res = new TResult<float>();

            try
            {
                ushort[] buf = _master.ReadHoldingRegisters(SlaveAddress, startAddress, 2);
                res.Value = BitConverter.ToSingle(BitConverter.GetBytes(buf[0] | (uint)(buf[1] << 16)), 0);
                res.Success = true;
            } 
            catch (Exception)
            {
                
            }

            return res;
        }

        /// <summary>
        /// 读取连续D寄存器
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        /// <param name="num">最大寄存器数:65535</param>
        /// <returns></returns>
        public TResult<ushort[]> ReadHoldingRegisters(ushort startAddress, int num)
        {
            List<ushort> ans = new List<ushort>();
            var res = new TResult<ushort[]>();

            if (num <= 0)
            {
                return res;
            }

            // 经测试，nmodbus单次读取最多100个ushort,对于超过100个的读取操作需要分批次读取
            try
            {
                for (int i = 0; i < (num - 1) / 100 + 1; i++)
                {
                    ushort[] buf = _master.ReadHoldingRegisters(SlaveAddress,
                        (ushort)(startAddress + i * 100),
                        (ushort)((num < (i + 1) * 100) ? (num - i * 100) : 100));
                    ans.AddRange(buf.ToList());
                }
                res.Value = ans.ToArray();
                res.Success = true;
            }
            catch (Exception)
            {
                
            }

            return res;
        }

        public TResult WriteSingleCoil(ushort startAddress, bool value)
        {
            var res = new TResult();
            try
            {
                _master.WriteSingleCoil(SlaveAddress, startAddress, value);
                res.Success = true;
            }
            catch (Exception)
            {
                
            }

            return res;
        }

        public TResult WriteSingleRegister(ushort startAddress, ushort value)
        {
            var res = new TResult();
            try
            {
                _master.WriteSingleRegister(SlaveAddress, startAddress, value);
                res.Success = true;
            }
            catch (Exception)
            {
                
            }
            return res;
        }

        public TResult WriteMultipleCoils(ushort startAddress, bool[] value)
        {
            var res = new TResult();

            try
            {
                _master.WriteMultipleCoils(SlaveAddress, startAddress, value);
                res.Success = true;
            }
            catch (Exception)
            {
                
            }

            return res;
        }

        /// <summary>
        /// 读取单个16bits无符号整数
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        public TResult WriteSingleWord(ushort startAddress, ushort value)
        {
            var res = new TResult();
            try
            {
                _master.WriteMultipleRegisters(SlaveAddress, startAddress, new ushort[] { value });
                res.Success = true;
            }
            catch (Exception)
            {
                
            }
            return res;
        }

        /// <summary>
        /// 读取单个32bits整数
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        public TResult WriteDWord(ushort startAddress, int value)
        {
            var res = new TResult();
            ushort[] buf = new ushort[2];
            Buffer.BlockCopy(new int[] { value }, 0, buf, 0, 4);
            try
            {
                _master.WriteMultipleRegisters(SlaveAddress, startAddress, buf);
                res.Success = true;
            }
            catch (Exception)
            {
                
            }
            return res;
        }

        /// <summary>
        /// 读取单个32bits浮点数
        /// </summary>
        /// <param name="startAddress"></param>
        public TResult WriteFloat(ushort startAddress, float value)
        {
            var res = new TResult();
            ushort[] buf = new ushort[2];
            Buffer.BlockCopy(new float[] { value }, 0, buf, 0, 4);
            try
            {
                _master.WriteMultipleRegisters(SlaveAddress, startAddress, buf);
                res.Success = true;
            }
            catch (Exception)
            {
                
            }

            return res;
        }

        /// <summary>
        /// 写入连续D寄存器
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        /// <param name="value"></param>
        public TResult WriteMultipleRegisters(ushort startAddress, ushort[] value)
        {
            var res = new TResult();
            if (startAddress < 0 || value == null || value.Length == 0)
            {
                return res;
            }

            // 经测试，nmodbus单次写入最多100个ushort,对于超过100个的读取操作需要分批次读取
            try
            {
                int len = value.Length;
                for (int i = 0; i < ((len - 1) / 100) + 1; i++)
                {
                    _master.WriteMultipleRegisters(SlaveAddress,
                        (ushort)(startAddress + i * 100),
                        value.Skip(i * 100).Take((len < (i + 1) * 100) ? (len - i * 100) : 100).ToArray());
                }
                res.Success = true;
            }
            catch (Exception)
            {
                
            }

            return res;
        }

        public TResult<ushort[]> ReadWriteMultipleRegisters(ushort startReadAddress,
            ushort numberOfPointsToRead,
            ushort startWriteAddress,
            ushort[] writeData)
        {
            var res = new TResult<ushort[]>();

            try
            {
                res.Value = _master.ReadWriteMultipleRegisters(SlaveAddress, startReadAddress, numberOfPointsToRead, startWriteAddress, writeData);
                res.Success = true;
            }
            catch (Exception)
            {
                
            }

            return res;
        }

        /// <summary>
        /// 仅供测试使用
        /// </summary>
        /// <param name="startAddr"></param>
        /// <param name="num"></param>
        public void TestForWriteMultipleRegisters(ushort startAddr, int num)
        {
            ushort[] buf = new ushort[num];
            for (int i = 0; i < num; i++)
            {
                buf[i] = (ushort)(i + 1);
            }

            Stopwatch sw = new Stopwatch();
            Console.WriteLine($"写入寄存器数量：{buf.Length}");
            sw.Start();
            WriteMultipleRegisters(startAddr, buf);
            sw.Stop();
            Console.WriteLine($"本次通讯耗时：{sw.ElapsedMilliseconds}ms");
        }

        /// <summary>
        /// 仅供测试使用
        /// </summary>
        /// <param name="startAddr"></param>
        /// <param name="num"></param>
        /// <param name="times"></param>
        public void TestForReadWriteMultipleRegisters(ushort startAddr, ushort num, int times)
        {
            ushort[] buf = new ushort[num];
            for (int i = 0; i < num; i++)
            {
                buf[i] = (ushort)(i + 1);
            }

            Console.WriteLine($"读写寄存器数量：{buf.Length}\n读写次数：{times}");

            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < times; i++)
            {
                WriteMultipleRegisters(startAddr, buf);
                //buf = ReadHoldingRegisters(startAddr, num);
            }
            sw.Stop();

            Console.WriteLine($"本次通讯耗时：{sw.ElapsedMilliseconds}ms");
        }
    }
}
